package com.uuah.server.util.store;

import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeUnit;

import com.uuah.config.i18n.CoreMessages;

/**
 *<code>InMemoryObjectStore</code> implements an optionally bounded in-memory
 * store for message IDs with periodic expiry of old entries. The bounded size
 * is a <i>soft</i> limit and only enforced periodically by the expiry process;
 * this means that the store may temporarily exceed its maximum size between
 * expiry runs, but will eventually shrink to its configured size.
 *
 * <p>
 * update record:updator，update time，update content and version
 * </p>
 *
 * @author <a href="jonny_quan@hotmail.com">jonny</a>
 * @date 2009 2009-3-27 下午03:20:03
 * @version 1.0.0
 */
public class InMemoryObjectStore extends AbstractMonitoredObjectStore {
	protected ConcurrentSkipListMap store;

	public InMemoryObjectStore() {
		this.store = new ConcurrentSkipListMap();
	}

	public boolean containsObject(String id) throws Exception {
		if (id == null) {
			throw new IllegalArgumentException(CoreMessages.objectIsNull("id")
					.toString());
		}

		// this is a relaxed check so we don't need to synchronize on the store.
		return store.values().contains(new StoredObject(id, null));
	}

	public boolean storeObject(String id, Object item) throws Exception {
		if (id == null) {
			throw new IllegalArgumentException(CoreMessages.objectIsNull("id")
					.toString());
		}

		StoredObject obj = new StoredObject(id, item);
		synchronized (store) {
			if (store.values().contains(obj)) {
				return false;
			}

			boolean written = false;
			while (!written) {
				written = (store.putIfAbsent(new Long(System.nanoTime()), obj) == null);
			}

			return true;
		}
	}

	public Object retrieveObject(String id) throws Exception {
		StoredObject obj = (StoredObject) store.get(id);
		if (obj != null) {
			return obj.getItem();
		}
		return null;
	}

	public boolean removeObject(String id) throws Exception {
		StoredObject obj = (StoredObject) store.get(id);
		if (obj != null) {
			return store.remove(obj) != null;
		}
		return true;
	}

	public final void expire() {
		// this is not guaranteed to be precise, but we don't mind
		int currentSize = store.size();

		// first trim to maxSize if necessary
		int excess = (currentSize - maxEntries);
		if (excess > 0) {
			while (currentSize > maxEntries) {
				store.pollFirstEntry();
				currentSize--;
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Expired " + excess + " excess entries");
			}
		}

		// expire further if entry TTLs are enabled
		if (entryTTL > 0 && currentSize != 0) {
			final long now = System.nanoTime();
			int expiredEntries = 0;
			Map.Entry oldestEntry;

			purge: while ((oldestEntry = store.firstEntry()) != null) {
				Long oldestKey = (Long) oldestEntry.getKey();
				long oldestKeyValue = oldestKey.longValue();

				if (TimeUnit.NANOSECONDS.toMillis(now - oldestKeyValue) >= entryTTL) {
					store.remove(oldestKey);
					expiredEntries++;
				} else {
					break purge;
				}
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Expired " + expiredEntries + " old entries");
			}
		}
	}

	protected static class StoredObject {
		private String id;
		private Object item;

		public StoredObject(String id, Object item) {
			this.id = id;
			this.item = item;
		}

		public String getId() {
			return id;
		}

		public Object getItem() {
			return item;
		}

		public boolean equals(Object o) {
			if (this == o) {
				return true;
			}
			if (o == null || getClass() != o.getClass()) {
				return false;
			}

			StoredObject that = (StoredObject) o;

			if (!id.equals(that.id)) {
				return false;
			}

			return true;
		}

		public int hashCode() {
			return id.hashCode();
		}

		public String toString() {
			final StringBuffer sb = new StringBuffer();
			sb.append("StoredObject");
			sb.append("{id='").append(id).append('\'');
			sb.append(", item=").append(item);
			sb.append('}');
			return sb.toString();
		}
	}

}
